/*
 *  Copyright (C) 2010:
 *                 Buzzanca Marco <marco.bzn@gmail.com>
 *         Monaco Davide Giuseppe <black.ralkass@gmail.com>
 *              Petralia Veronica <veronicapetralia@gmail.com>
 *
 * This file is part of DS2011BMP
 *
 * DS2011BMP is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * DS2011BMP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *
 * NOTE: this file should not be included directly,
 *       Include udpinterface.h instead.
 */

/**
 * @file udpinterface.cpp
 * @author Marco Buzzanca
 *
 * This file contains UDPSender, UDPReceiver and UDPSenderReceiver 
 * class declaration and implementation.
 */

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/cstdint.hpp>
#include <boost/asio.hpp>
#include <cstring>
#include <sstream>
#include <stdio.h>
#include <exception>
#include "workflow.h"
#include "address.h"
#include "taskdescriptor.h"

/**
 * UDPSender
 *
 * Abstract class that defines common methods and attributes for sending data
 * over UDP. Inheriting classes must implement send() methods.
 *
 * TODO: Error/exception handling on operator << call on non-serializable objects.
 *
 */
class UDPSender
{
public:
    /**
     * @brief Sends data over UDP.
     *
     * This class method uses provided buffer to acquire data, and should
     * return the number of bytes written.
     */
	virtual size_t send(char*, size_t) = 0;

    /**
     * @brief Sends a size_t variable over UDP.
     *
     * Useful if you have an item larger than 1024 bytes, so you can warn the
     * receiver that item size requires more than one packet.
     */
	virtual size_t send(const size_t&) = 0;

    /**
     * @brief Sends a string over UDP.
     */
	virtual size_t send(const std::string&) = 0;

  /**
   * @author Davide Giuseppe Monaco
   * @brief Send a Workflow over UDP using boost serialization.
   *
   * Mind that it is necessary to call resolve() on Server/Client before
   * using operator << .
   *
   * @param w_ the Workflow to serialize and send via UDP.
   *
   * @deprecated Use templated method instead.
   */
  UDPSender& operator << (const Workflow& w_)
  {
    // stringstream used to serialize
    std::stringstream oss;

    boost::archive::text_oarchive oa(oss);

    // textarchive provided workflow and take out the string
    // contained in the bound stringstream.
    oa << w_;
    send(oss.str());

    return (*this);
  }

  /**
   * @author Davide Giuseppe Monaco
   * @brief Send a TaskDescriptor over UDP using boost serialization.
   *
   * Mind that it is necessary to call resolve() on Server/Client before
   * using operator << .
   *
   * @param td_ the TaskDescriptor to serialize and send via UDP.
   *
   * @deprecated Use templated method instead.
   */
  UDPSender& operator << (const TaskDescriptor& td_)
  {
    // stringstream used to serialize
    std::stringstream oss;

    boost::archive::text_oarchive oa(oss);

    // textarchive provided workflow and take out the string
    // contained in the bound stringstream.
    oa << td_;
    send(oss.str());

    return (*this);
  }

    /**
     * @brief Send an Address over UDP using boost serialization.
     *
     * Mind that it is necessary to call resolve() on Server/Client before
     * using operator << .
     *
     * @param a_ the Address to serialize and send via UDP.
     */
    UDPSender& operator << (const Address& a_)
    {
        // stringstream used to serialize
        std::stringstream oss;

        boost::archive::text_oarchive oa(oss);

        // textarchive provided workflow and take out the string
        // contained in the bound stringstream.
        oa << a_;
        send(oss.str());

        return (*this);
    }

   /**
    * @brief Send a Serializable object over UDP using boost serialization.
    *
    * Mind that it is necessary to call resolve() on Client (or bind() on
    * Server) before using operator >>.
    *
    * @note An object is serializable if it has a serialize() private method.
    *       See boost::serialize documentation.
    *
    * @param obj the object to serialize and send via UDP.
    */
    template <typename Serializable>
    UDPSender& operator << (const Serializable& obj)
    {
        // stringstream used to serialize
        std::stringstream oss;

        boost::archive::text_oarchive oa(oss);

        // textarchive provided object and take out the string
        // contained in the bound stringstream.
        oa << obj;
        send(oss.str());

        return (*this);
    }

};

/**
 * UDPReceiver
 *
 * Abstract class that defines common methods and attributes for receivng data
 * over UDP. Inheriting classes must implement receive() methods.
 */
class UDPReceiver
{
public:
    /**
     * @brief Maximum receive buffer length.
     */
     static const size_t MAX_BUF_LEN = 65536;

    /**
     * @brief Accepts data over UDP.
     *
     * This class method uses provided buffer to save data, and should return
     * the number of bytes read.
     */
	virtual size_t receive(char*, size_t) = 0;

    /**
     * @brief Accepts a size_t variable over UDP.
     *
     * Useful if you are waiting for an item larger than 1024 bytes, so you can
     * be warned that more than one packet is incoming.
     */
	virtual size_t receive(size_t&) = 0;

    /**
     * @brief Receive a Serializable object from UDP using boost serialization.
     *
     * Mind that it is necessary to call resolve() on Client (or bind() on
     * Server) before using operator >>.
     *
     * @note An object is serializable if it has a serialize() private method.
     *       See boost::serialize documentation.
     *
     * @param obj the Object which will receive unserialization data.
     */
    template <typename Serializable>
    UDPReceiver& operator >> (Serializable& obj)
    {
        boost::int8_t buffer[MAX_BUF_LEN];
        size_t request_length = receive(buffer, MAX_BUF_LEN);

        // stringstream used to unserialize
        std::stringstream iss;
        iss.write(buffer, request_length);

        boost::archive::text_iarchive ia(iss);

        // writes into provided object content of textarchive.
        ia >> obj;

        return (*this);
    }

};

/**
 * UDPSenderReceiver
 *
 * Abstract class that defines common methods and attributes for UDP
 * Communication. Inheriting classes must implement send() and receive()
 * methods.
 *
 * TODO: Check inheritance in order to avoid unnecessary redefinitions.
 *
 */
class UDPSenderReceiver : public UDPSender, public UDPReceiver
{
private:
    static const size_t _FILEBUF_LEN = 1024;

public:
    /**
     * @brief Sends data over UDP.
     *
     * This class method uses provided buffer to acquire data, and should
     * return the number of bytes written.
     */
	virtual size_t send(char*, size_t) = 0;

    /**
     * @brief Sends a size_t variable over UDP.
     *
     * Useful if you have an item larger than 1024 bytes, so you can warn the
     * receiver that item size requires more than one packet.
     */
	virtual size_t send(const size_t&) = 0;

    /**
     * @brief Sends a string over UDP.
     */
	virtual size_t send(const std::string&) = 0;

    /**
     * @brief Accepts data over UDP.
     *
     * This class method uses provided buffer to save data, and should return
     * the number of bytes read.
     */
	virtual size_t receive(char*, size_t) = 0;

    /**
     * @brief Accepts a size_t variable over UDP.
     *
     * Useful if you are waiting for an item larger than 1024 bytes, so you can
     * be warned that more than one packet is incoming.
     */
	virtual size_t receive(size_t&) = 0;

    /**
     * @author Marco Buzzanca
     * @brief Sends a file over UDP.
     *
     * Mind that it is necessary to call resolve() on Client (or bind() on
     * Server) before using receiveFile().
     *
     * @param source_file path to local file from which data will be read.
     * @return bytes written to socket, 0 if it fails.
     */
	size_t sendFile(const char* source_file)
	{
	    FILE* fp = fopen(source_file, "rb");

        if (fp == NULL)
            return 0;

        char buf[_FILEBUF_LEN];
        size_t file_size, received_size;

        // Gets file size.
        fseek(fp, 0L, SEEK_END);
        file_size = ftell(fp);

        // Submits file size, so the receiver knows how many packets will
        // be sent.
        send(file_size);
        receive(received_size);

        if (file_size != received_size)
        {
            fclose(fp);
            return 0;
        }

        // Sets file pointer to the beginning of the file.
        fseek(fp, 0, SEEK_SET);

        size_t bytes_read;
        size_t bytes_sent = 0;

        while (feof(fp) == 0)
        {
            bytes_read = fread(buf, sizeof(char), _FILEBUF_LEN, fp);
            bytes_sent += send(buf, bytes_read);
        }

        fclose(fp);

        return bytes_sent;
	}

    /**
     * @brief Receives a file over UDP.
     *
     * Mind that it is necessary to call resolve() on Client (or bind() on
     * Server) before using receiveFile().
     *
     * @param target_file path to local file in which incoming data will be
     *        written.
     * @return bytes read from socket, 0 if it fails.
     */
	size_t receiveFile(const char* target_file)
	{
	    FILE* fp = fopen(target_file, "wb");

        if (fp == NULL)
            return 0;

        char buf[_FILEBUF_LEN];
        size_t received_size;

        // Echoes file size, so the sender knows we got it right.
        receive(received_size);
        send(received_size);

        size_t bytes_received;
        size_t bytes_written = 0;

        while (bytes_written < received_size)
        {
            bytes_received = receive(buf, _FILEBUF_LEN);
            fwrite(buf, sizeof(char), bytes_received, fp);
            bytes_written += bytes_received;
        }

        fclose(fp);

        return bytes_written;
	}

    /**
     * @brief Send a Serializable object over UDP using boost serialization.
     *
     * Mind that it is necessary to call resolve() on Client (or bind() on
     * Server) before using operator >>.
     *
     * @note An object is serializable if it has a serialize() private method.
     *       See boost::serialize documentation.
     *
     * @param obj the object to serialize and send via UDP.
     */
    template <typename Serializable>
    UDPSender& operator << (const Serializable& obj)
    {
        // stringstream used to serialize
        std::stringstream oss;

        boost::archive::text_oarchive oa(oss);

        // textarchive provided object and take out the string
        // contained in the bound stringstream.
        oa << obj;
        send(oss.str());

        return (*this);
    }

    /**
     * @brief Receive a Serializable object from UDP using boost serialization.
     *
     * Mind that it is necessary to call resolve() on Client (or bind() on
     * Server) before using operator >>.
     *
     * @note An object is serializable if it has a serialize() private method.
     *       See boost::serialize documentation.
     *
     * @param obj the Object which will receive unserialization data.
     */
    template <typename Serializable>
    UDPReceiver& operator >> (Serializable& obj)
    {
        char buffer[MAX_BUF_LEN];
        size_t request_length = receive(buffer, MAX_BUF_LEN);

        // stringstream used to unserialize
        std::stringstream iss;
        iss.write(buffer, request_length);

        boost::archive::text_iarchive ia(iss);

        // writes into provided object content of textarchive.
        ia >> obj;

        return (*this);
    }

};
